package com.yss.sofa.report.engine.util;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yss.sofa.report.engine.ConnectionFactory;
import com.yss.sofa.report.engine.Context;
import com.yss.sofa.report.engine.DataSetData;

import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovySystem;
import groovy.lang.Script;

public final class ScriptUtil {
	public static class Db {
		private static final Logger LOGGER=LoggerFactory.getLogger(Db.class);
		private final Context context;
		public Db(Context context){
			super();
			this.context=context;
		}
		public synchronized int type(CharSequence db) throws Exception{
			ConnectionFactory factroy=context.get(Context.CONTEXT_KEY_CONNECTION_FACTORY, ConnectionFactory.class);
			return factroy.getDbType(db.toString());
		}
		public synchronized List<Map<String,String>> query(CharSequence db,CharSequence sql) throws Exception{
			long begintime=System.currentTimeMillis();
			ConnectionFactory factroy=context.get(Context.CONTEXT_KEY_CONNECTION_FACTORY, ConnectionFactory.class);
			Connection connection;
			try {
				connection = factroy.getConnection(db.toString());
				//connection.setAutoCommit(true);
			} catch (Exception e) {
				throw new Exception("无法连接到数据库["+db+"]", e);
			}
			PreparedStatement ps=null;
			ResultSet resultSet=null;
			try {
				ps=connection.prepareStatement(sql.toString());
				List<Map<String,String>> result=new LinkedList<Map<String,String>>();
				resultSet=ps.executeQuery();
				ResultSetMetaData meta=resultSet.getMetaData();
				int colCount=meta.getColumnCount();
				while(resultSet.next()){
					Map<String,String> record=new HashMap<String, String>();
					for(int i=0;i<colCount;i++){
						record.put(meta.getColumnName(i+1).toLowerCase(), resultSet.getString(i+1));
					}
					result.add(record);
				}
				int maxsize = getMaxQuerySize();
				if(result.size()>maxsize){
					throw new Exception(" Script数据集结果数量太大，请优化查询条件!!");
				}
				
				long endtime=System.currentTimeMillis();
				if(LOGGER.isDebugEnabled()){
					LOGGER.debug("在数据库["+db+"]上执行SQL：\n"+sql+"\n的耗时为"+(endtime-begintime)/1000+"秒");
				}
				List<Map<String,String>> _=new ArrayList<Map<String,String>>(result.size());
				_.addAll(result);
				return _;
			} catch (SQLException e) {
				throw new Exception("无法在数据库["+db+"]上执行SQL：\n"+sql+"\n", e);
			} finally {
				if(resultSet!=null) resultSet.close();
				if(ps!=null) ps.close();
				connection.close();
			}
		}
		private int getMaxQuerySize() {
			String maxsize = ParamtersConfig.getInstance().getParameter("report.scriptDataSet.query.maxsize");
			if(StringUtils.isEmpty(maxsize)){
				return 50000;
			}
			//int maxsize = SOFARuntime.getInstance().getIntParameter("report.scriptData.query.maxsize");
			return Integer.parseInt(maxsize);
		}
		
		public synchronized DataSetData data(CharSequence db,CharSequence sql) throws Exception{
			long begintime=System.currentTimeMillis();
			ConnectionFactory factroy=context.get(Context.CONTEXT_KEY_CONNECTION_FACTORY, ConnectionFactory.class);
			Connection connection;
			try {
				connection = factroy.getConnection(db.toString());
				//connection.setAutoCommit(true);
			} catch (Exception e) {
				throw new Exception("无法连接到数据库["+db+"]", e);
			}
			PreparedStatement ps=null;
			ResultSet resultSet=null;
			try {
				ps=connection.prepareStatement(sql.toString());
				DataSetData result=new DataSetData();
				resultSet=ps.executeQuery();
				ResultSetMetaData meta=resultSet.getMetaData();
				int colCount=meta.getColumnCount();
				for(int i=0;i<colCount;i++){
					String field=meta.getColumnName(i+1).toLowerCase();
					result.addField(field);
				}
				while(resultSet.next()){
					Map<String,String> record=new HashMap<String, String>();
					for(int i=0;i<colCount;i++){
						record.put(meta.getColumnName(i+1).toLowerCase(), resultSet.getString(i+1));
					}
					result.addRecord(record);
				}
				long endtime=System.currentTimeMillis();
				if(LOGGER.isDebugEnabled()){
					LOGGER.debug("在数据库["+db+"]上执行SQL：\n"+sql+"\n的耗时为"+(endtime-begintime)/1000+"秒");
				}
				return result.convert();
			} catch (SQLException e) {
				throw new Exception("无法在数据库["+db+"]上执行SQL：\n"+sql+"\n", e);
			} finally {
				if(resultSet!=null) resultSet.close();
				if(ps!=null) ps.close();
				connection.close();
			}
		}
	}
	public static class Auth {
		private final String userId;
		public Auth(String userId){
			super();
			this.userId=userId;
		}
		public synchronized List<String> items(CharSequence function,CharSequence operation,CharSequence auth) throws Exception{
		    // FIXME 拥有所有权限
		    List<String> list = new ArrayList<String>();
		    list.add("*");
			return list;
		}
	}
	public static class Util {
		private final Map<String,Serializable> params;
		public Util(Map<String,Serializable> params){
			super();
			this.params=params;
		}
		public synchronized Map<Serializable,String> map(CharSequence code) throws Exception{
			if(!params.containsKey(code)){
				throw new Exception("没有找到参数["+code+"]");
			}
			String __code__="__"+code+"__";
			if(!params.containsKey(__code__)){
				throw new Exception("没有找到参数["+__code__+"]");
			}
			Serializable key=params.get(code);
			Serializable value=params.get(__code__);
			if(key==null || !key.getClass().isArray()){
				throw new Exception("参数["+code+"]不是数组");
			}
			if(value==null || !(value instanceof String)){
				throw new Exception("参数["+__code__+"]不是字符串");
			}
			Serializable[] keys=(Serializable[]) key;
			String[] values=((String)value).split(",");
			if(keys.length!=values.length){
				throw new Exception("参数["+code+"]与参数["+__code__+"]的值数量不一致");
			}
			Map<Serializable,String> map=new LinkedHashMap<Serializable, String>();
			for(int i=0;i<keys.length;i++){
				map.put(keys[i], values[i]);
			}
			return map;
		}
		public synchronized String ins(CharSequence field,CharSequence value) throws Exception{
			try {
				String[] values=value.toString().split(",");
				StringBuilder sb=new StringBuilder();
				for(String v:values){
					sb.append("(");
					sb.append(field);
					sb.append("=");
					sb.append("'"+v.replaceAll("'", "''")+"'");
					sb.append(")");
					sb.append(" or ");
				}
				sb.append("(1=2)");
				return "("+sb.toString()+")";
			} catch (Exception e) {
				throw new Exception("util.ins方法执行错误", e);
			}
		}
		public synchronized String ins(CharSequence field,CharSequence[] values) throws Exception{
			try {
				StringBuilder sb=new StringBuilder();
				for(CharSequence v:values){
					sb.append("(");
					sb.append(field);
					sb.append("=");
					sb.append("'"+v.toString().replaceAll("'", "''")+"'");
					sb.append(")");
					sb.append(" or ");
				}
				sb.append("(1=2)");
				return "("+sb.toString()+")";
			} catch (Exception e) {
				throw new Exception("util.ins方法执行错误", e);
			}
		}
	}
	public static class Cache {
		private static final com.yss.sofa.framework.cache.Cache CACHE=com.yss.sofa.framework.cache.Cache.getInstance();
		private final String REGION;
		public Cache(String region){
			this.REGION=region;
		}
		public synchronized <T extends Serializable> T get(CharSequence key,Class<T> type){
			return CACHE.get(key, type);
		}
		public synchronized Object get(CharSequence key){
			return CACHE.get(key);
		}
		public synchronized void put(CharSequence key,Serializable value){
			CACHE.put(REGION, key, value);
		}
		public synchronized void remove(CharSequence key){
			CACHE.remove(key);
		}
		public synchronized void clear(){
			CACHE.clear(REGION);
		}
	}
	private static final Map<String,Integer> CODE_VERSION_CACHE=new ConcurrentHashMap<String, Integer>();
	private static final Map<String,Class<?>> CLASS_CACHE=new ConcurrentHashMap<String, Class<?>>();
	public static synchronized void clearAll(){
		CODE_VERSION_CACHE.clear();
		CLASS_CACHE.clear();
	}
	private static synchronized Class<?> getScriptClass(String className,String sourceCode){
		Class<?> cls=null;
		int hashcode=sourceCode.hashCode();
		if(CODE_VERSION_CACHE.containsKey(className)){
			Integer version=CODE_VERSION_CACHE.get(className);
			if(hashcode==version){
				cls=CLASS_CACHE.get(className);
			}
		}
		if(cls==null){
			//@SuppressWarnings("resource")
			GroovyClassLoader loader=new GroovyClassLoader(ScriptUtil.class.getClassLoader());
			try {
				cls=loader.parseClass(new GroovyCodeSource(sourceCode, className, ""), false);
				CODE_VERSION_CACHE.put(className, hashcode);
				CLASS_CACHE.put(className, cls);
			} finally {
				loader.clearCache();
				//loader.close(); //@since 1.7
			}
		}
		return cls;
	}
	@SuppressWarnings("unchecked")
	public static final <T> T execute(String className,String sourceCode,Map<String,Object> vars, Class<T> clazz, boolean allowNull) throws Exception{
		/*
		GroovyShell shell=new GroovyShell(ScriptUtil.class.getClassLoader());
		if(vars!=null){
			Set<String> keys=vars.keySet();
			for(String key:keys){
				Object value=vars.get(key);
				shell.setVariable(key, value);
			}
		}
		Object result=null;
		try {
			result = shell.evaluate(sourceCode);
		} catch (Throwable e) {
			throw new Exception("Groovy脚本运行时错误",e);
		}
		*/
		Class<?> cls=getScriptClass(className, sourceCode);
		Object result=null;
		try {
			Binding binding=new Binding(vars);
			Script script=InvokerHelper.createScript(cls, binding);
			result = script.run();
			GroovySystem.getMetaClassRegistry().removeMetaClass(script.getMetaClass().getClass());
			GroovySystem.getMetaClassRegistry().removeMetaClass(script.getClass());
		} catch (Exception e) {
			throw new Exception("Groovy脚本运行时错误",e);
		}
		if(result==null){
			if(allowNull){
				return null;
			} else {
				throw new Exception("Groovy脚本未返回有效的值");
			}
		}
		return (T)result;
	}
}
